/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2020年9月18日
 * V4.0
 */
package com.jphenix.share.tools;

import com.jphenix.standard.docs.ClassInfo;

/**
 * DES加解密工具类
 * com.jphenix.share.tools.Des
 * 
 * 为了避免JDK 中的 aes加密限制，
 * 开发了当前Des加解密类，绕过JDK限制，不用给JDK打补丁
 * 
 * 2021-03-01 修改了解密时，如果末尾字节是8个8，解密出错的问题
 * 
 * @author MBG 2020年9月18日
 */
@ClassInfo({ "2021-03-01 17:15", "DES加解密工具类" })
public class Des {

  // #region 变量声明区
  // 初始置换
  private final int[] IP = { 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30,
      22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45,
      37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 };
  // 逆初始置换
  private final int[] IP_1 = { 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54,
      22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2,
      42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 };// 手残，数组数据没写全
  // E扩展
  private final int[] E = { 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17,
      16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 };
  // P置换
  private final int[] P = { 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3,
      9, 19, 13, 30, 6, 22, 11, 4, 25 };
  private static int[][][] S_Box = {
      { { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7 },
          { 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8 },
          { 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0 },
          { 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 } },
      { { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10 },
          { 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5 },
          { 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15 },
          { 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 } },
      { { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8 },
          { 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1 },
          { 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7 },
          { 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 } },
      { { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15 },
          { 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9 },
          { 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4 },
          { 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 } },
      { { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9 },
          { 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6 },
          { 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14 },
          { 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 } },
      { { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11 },
          { 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8 },
          { 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6 },
          { 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 } },
      { { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1 },
          { 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6 },
          { 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2 },
          { 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 } },
      { { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7 },
          { 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2 },
          { 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8 },
          { 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } } };
  // PC-1
  private final int[] PC1 = { 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27,
      19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
      13, 5, 28, 20, 12, 4 };
  // PC-2
  private final int[] PC2 = { 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20,
      13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 };
  // Schedule of Left Shifts
  private final int[] LFT = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
  // #endregion

  private int[][] mKeySub = null; //秘钥

  /**
   * 构造函数
   */
  public Des(){
    super();
  }

  /**
   * 构造函数
   */
  public Des(String key){
    super();
    if(key!=null && key.length()>0){
      mKeySub = generateKeys(key);
    }
  }

  /**
   * 设置秘钥
   */
  public void setKey(String key){
    if(key==null || key.length()<1){
      return;
    }
    mKeySub = generateKeys(key);
  }
  
  /**
   * 进行Des加密
   * @param content 待加密内容
   * @param key     秘钥
   * @return 加密后的16进制格式字符串（大写）
   */
  public static String enc(String content, String key) {
    return (new Des()).encode(content,key);
  }

  /**
   * 进行Des解密
   * 
   * @param content 待解密内容（加密的大写16进制字符格式字符串）
   * @parma key 秘钥
   * @return 解密后的字符串
   */
  public static String dec(String content, String key) {
    return (new Des()).decode(content,key);
  }

  /**
   * 执行解密
   * @param content 待解密的大写16进制字符格式字符串
   * @return        解密后的内容
   */
  public String decode(String content){
    if(mKeySub==null){
      System.err.println("Des Error:The Parameter[key] Not Found");
      return null;
    }
    return new String(deal(parseHexStr2Byte(content),mKeySub,false));
  }

  /**
   * 执行解密
   * @param content 待解密的大写16进制字符格式字符串
   * @param key     秘钥
   * @return        解密后的内容
   */
  public String decode(String content,String key){
    int[][] keySub = null; //16个子秘钥
    if(key==null || key.length()<1){
      keySub = mKeySub;
    }else{
      keySub = generateKeys(key);
    }
    if(keySub==null){
      System.err.println("Des Error:The Parameter[key] Not Found");
      return null;
    }
    byte[] bytes = deal(parseHexStr2Byte(content),keySub, false);
    return new String(bytes);
  }

  /**
   * 执行加密
   * @param content 待加密内容
   * @return        加密后的大写16进制字符格式字符串
   */
  public String encode(String content){
    if(mKeySub==null){
      System.err.println("Des Error:The Parameter[key] Not Found");
      return null;
    }
    return parseByte2HexStr(deal(content.getBytes(),mKeySub, true));
  }

  /**
   * 执行加密
   * @param content 待加密内容
   * @param key     秘钥（为空时，取构造函数传入的秘钥）
   * @return        加密后的大写16进制字符格式字符串
   */
  public String encode(String content,String key){
    int [][] keySub = null; //16个子秘钥
    if(key==null || key.length()<1){
      keySub = mKeySub;
    }else{
      keySub = generateKeys(key);
    }
    if(keySub==null){
      System.err.println("Des Error:The Parameter[key] Not Found");
      return null;
    }
    try{
      return parseByte2HexStr(deal(content.getBytes("UTF-8"),keySub, true));
    }catch(Exception e){
      e.printStackTrace();
      return parseByte2HexStr(deal(content.getBytes(),keySub, true));
    }
  }




  /**
   * 执行加密、解密
   * 
   * @param p      待处理内容
   * @param subKey 16个子秘钥
   * @param isEnc  是否为加密。false解密
   */
  private byte[] deal(byte[] p, int[][] subKey, boolean isEnc) {
    int originLength = p.length;
    int gNum;
    int rNum;
    byte[] pPadding;
    gNum = originLength / 8;
    rNum = 8 - (originLength - gNum * 8);// 8不填充
    /**** 填充 ********/
    //注意：只有在加密的时候，如果被8整除后，需要补8个8，解密的时候如果被8整除，不用补8
    //      因为解密时，通常都能被8整除的
    //在长度可以被8整除的时候，jdk在字符串的末尾补了8个8，在这个代码中并没有补8个8
    //为了和sdk保持一致，这里也补了8个8
    //为啥不用JDK原生的：因为JDK原生代码涉及到JDK中美国的一个限制：aes加密限制，
    //需要打补丁才支持，用了咱这个代码就不用给JDK打补丁了
    if (isEnc) {
      pPadding = new byte[originLength + rNum];
      System.arraycopy(p, 0, pPadding, 0, originLength);
      for (int i = 0; i < rNum; i++) {
        pPadding[originLength + i] = (byte) rNum;
      }
    } else {
      //解码时，rNum肯定为8，以为在加密时都做了补全。如果不为8，属于非法字符串
      pPadding = p;
    }
    gNum = pPadding.length / 8;
    byte[] fp = new byte[8];
    byte[] resultData = new byte[pPadding.length];
    for (int i = 0; i < gNum; i++) {
      System.arraycopy(pPadding, i * 8, fp, 0, 8);
      System.arraycopy(descryUnit(fp, subKey, isEnc), 0, resultData, i * 8, 8);
    }
    if(!isEnc && resultData.length>0){
      /*
       * 解密：
       * 
       * 在加密时，被加密的字符串长度如果不是8的倍数，会用一个字符填充到8的倍数。
       * 这个填充值就是原字符长度与8的倍数相差的值（这个值肯定小于8）
       * 
       * 在ascII码中，大于0小于等于8的字符实际上是控制字符。也就是说，在解密时，判断最后
       * 一个字符的值n是否在0到8之间，如果是说明后面的值是填充的，填充了n个相同字符
       */
      if(resultData[resultData.length-1]>0 && resultData[resultData.length-1]<=8){
        //需要去掉末尾的填充值
        byte[] fixArrs = new byte[resultData.length-resultData[resultData.length-1]];
        System.arraycopy(resultData, 0, fixArrs, 0, fixArrs.length);
        return fixArrs;
      }
    }
    return resultData;
  }

  /**
   * 加密解密处理
   */
  private byte[] descryUnit(byte[] p, int k[][], boolean isEnc) {
    int[] pBit = new int[64];
    StringBuilder stringBuilder = new StringBuilder();
    String pb;
    for (int i = 0; i < 8; i++) {
      pb = Integer.toBinaryString(p[i] & 0xff);
      while (pb.length() % 8 != 0) {
        pb = "0" + pb;
      }
      stringBuilder.append(pb);
    }
    String pStr = stringBuilder.toString();
    int pt;
    for (int i = 0; i < 64; i++) {
      pt = Integer.valueOf(pStr.charAt(i));
      if (pt == 48) {
        pt = 0;
      } else if (pt == 49) {
        pt = 1;
      } else {
        System.err.println("To bit error!");
      }
      pBit[i] = pt;
    }
    /*** IP置换 ***/
    int[] pIP = new int[64];
    for (int i = 0; i < 64; i++) {
      pIP[i] = pBit[IP[i] - 1];
    }
    if (isEnc) { // 加密
      for (int i = 0; i < 16; i++) {
        funcL(pIP, i, isEnc, k[i]);
      }
    } else if (!isEnc) { // 解密
      for (int i = 15; i > -1; i--) {
        funcL(pIP, i, isEnc, k[i]);
      }
    }
    int[] c = new int[64];
    for (int i = 0; i < IP_1.length; i++) {
      c[i] = pIP[IP_1[i] - 1];
    }
    byte[] cByte = new byte[8];
    for (int i = 0; i < 8; i++) {
      cByte[i] = (byte) ((c[8 * i] << 7) + (c[8 * i + 1] << 6) + (c[8 * i + 2] << 5) + (c[8 * i + 3] << 4)
          + (c[8 * i + 4] << 3) + (c[8 * i + 5] << 2) + (c[8 * i + 6] << 1) + (c[8 * i + 7]));
    }
    return cByte;
  }

  /**
   * 内部方法
   */
  private void funcL(int[] m, int times, boolean isEnc, int[] keyarray) {
    int[] l0 = new int[32];
    int[] r0 = new int[32];
    int[] l1 = new int[32];
    int[] r1 = new int[32];
    int[] f = new int[32];
    System.arraycopy(m, 0, l0, 0, 32);
    System.arraycopy(m, 32, r0, 0, 32);
    l1 = r0;
    f = funcF(r0, keyarray);
    for (int j = 0; j < 32; j++) {
      r1[j] = l0[j] ^ f[j];
      if ((!isEnc && (times == 0)) || (isEnc && (times == 15))) {
        m[j] = r1[j];
        m[j + 32] = l1[j];
      } else {
        m[j] = l1[j];
        m[j + 32] = r1[j];
      }
    }
  }

  /**
   * 内部方法
   */
  private int[] funcF(int[] rContent, int[] key) {
    int[] result = new int[32];
    int[] ek = new int[48];
    for (int i = 0; i < E.length; i++) {
      ek[i] = rContent[E[i] - 1] ^ key[i];
    }
    /******** S盒替换:由48位变32位，现分割ek，然后再进行替换 *********/
    int[][] s    = new int[8][6];
    int[] sAfter = new int[32];
    for (int i = 0; i < 8; i++) {
      System.arraycopy(ek, i * 6, s[i], 0, 6);
      int r = (s[i][0] << 1) + s[i][5];// 横坐标
      int c = (s[i][1] << 3) + (s[i][2] << 2) + (s[i][3] << 1) + s[i][4];// 纵坐标
      String str = Integer.toBinaryString(S_Box[i][r][c]);
      while (str.length() < 4) {
        str = "0" + str;
      }
      for (int j = 0; j < 4; j++) {
        int p = Integer.valueOf(str.charAt(j));
        if (p == 48) {
          p = 0;
        } else if (p == 49) {
          p = 1;
        } else {
          System.err.println("To bit error!");
        }
        sAfter[4 * i + j] = p;
      }
    }
    /****** S盒替换结束 *******/
    /**** P盒替代 ****/
    for (int i = 0; i < P.length; i++) {
      result[i] = sAfter[P[i] - 1];
    }
    return result;
  }

  /**
   * 生成子密钥
   * 
   * @param key 秘钥字符串
   * @return 16个子密钥
   */
  private int[][] generateKeys(String key) {
    // 构建返回值
    int[][] subKey = new int[16][48];
    while (key.length() < 8) {
      key = key + key;
    }
    key         = key.substring(0, 8);
    byte[] keys = key.getBytes();
    int[] kBit  = new int[64];
    // 取位值
    String kStr;
    int p;
    for (int i = 0; i < 8; i++) {
      kStr = Integer.toBinaryString(keys[i] & 0xff);
      while (kStr.length() < 8) {
        kStr = "0" + kStr;
      }
      if (kStr.length() < 8) {
        for (int t = 0; t < 8 - kStr.length(); t++) {
          kStr = "0" + kStr;
        }
      }
      for (int j = 0; j < 8; j++) {
        p = Integer.valueOf(kStr.charAt(j));
        if (p == 48) {
          p = 0;
        } else if (p == 49) {
          p = 1;
        } else {
          System.err.println("To bit error!");
        }
        kBit[i * 8 + j] = p;
      }
    }
    // k_bit是初始的64位长密钥，下一步开始进行替换
    /*********** PC-1压缩 ****************/
    int[] kNewBit = new int[56];
    for (int i = 0; i < PC1.length; i++) {
      kNewBit[i] = kBit[PC1[i] - 1]; // 这个减1注意点
    }
    /**************************/
    int[] c0 = new int[28];
    int[] d0 = new int[28];
    System.arraycopy(kNewBit, 0, c0, 0, 28);
    System.arraycopy(kNewBit, 28, d0, 0, 28);
    int[] c1;
    int[] d1;
    int[] tmp;
    for (int i = 0; i < 16; i++) {
      c1 = new int[28];
      d1 = new int[28];
      if (LFT[i] == 1) {
        System.arraycopy(c0, 1, c1, 0, 27);
        c1[27] = c0[0];
        System.arraycopy(d0, 1, d1, 0, 27);
        d1[27] = d0[0];
      } else if (LFT[i] == 2) {
        System.arraycopy(c0, 2, c1, 0, 26);
        c1[26] = c0[0];
        c1[27] = c0[1];// 这里手残之前写成c1
        System.arraycopy(d0, 2, d1, 0, 26);
        d1[26] = d0[0];
        d1[27] = d0[1];
      } else {
        System.err.println("LFT Error!");
      }
      tmp = new int[56];
      System.arraycopy(c1, 0, tmp, 0, 28);
      System.arraycopy(d1, 0, tmp, 28, 28);
      for (int j = 0; j < PC2.length; j++) {// PC2压缩置换
        subKey[i][j] = tmp[PC2[j] - 1];
      }
      c0 = c1;
      d0 = d1;
    }
    return subKey;
  }

  /**
   * 将字节转换成16进制字符
   * 
   * @param buf 待处理字节数组
   * @return 处理后的字符串
   */
  public String parseByte2HexStr(byte buf[]) {
    // 构建返回值
    StringBuffer sbf = new StringBuffer();
    for (int i = 0; i < buf.length; i++) {
      String hex = Integer.toHexString(buf[i] & 0xFF);
      if (hex.length() == 1) {
        hex = '0' + hex;
      }
      sbf.append(hex.toUpperCase());
    }
    return sbf.toString();
  }

  /**
   * 将16进制转换为字节数组
   * 
   * @param hexStr 待处理的16进制编码字符串
   * @return 处理后的字节数组
   */
  public byte[] parseHexStr2Byte(String hexStr) {
    if (hexStr.length() < 1) {
      return null;
    }
    int high, low;
    int size = hexStr.length() / 2;
    byte[] result = new byte[size]; // 构建返回值
    for (int i = 0; i < size; i++) {
      high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
      low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
      result[i] = (byte) (high * 16 + low);
    }
    return result;
  }
}
